package com.example.tangjiangxu.pulltorefreshtest;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.RotateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

public class RefreshableView extends LinearLayout implements View.OnTouchListener{
    private static final String TAG = "RefreshableView";

    /**
     * 下拉状态
     */
    public static final int STATUS_PULL_TO_REFRESH = 0;
    /**
     * 释放立即刷新状态
     */
    public static final int STATUS_RELAEASE_TO_REFRESH = 1;
    /**
     * 正在刷新状态
     */
    public static final int STATUS_REFRESHING = 2;
    /**
     * 刷新完成或未刷新状态
     */
    public static final int STATUS_REFRESH_FINISHED = 3;
    /**
     * 下拉头部回滚的速度
     */
    public static final int SCROLL_SPEED = -20;
    /**
     * 一分钟的毫秒值，用于判断上次的更新时间
     */
    private static final long ONE_MINUTE = 60 * 1000;
    /**
     * 一个小时的毫秒值，用于判断上次的更新时间
     */
    private static final long ONE_HOUR = 60 * ONE_MINUTE;
    /**
     * 一天的毫秒值，用于判断上次的更新时间
     */
    private static final long ONE_DAY = 24 * ONE_HOUR;
    /**
     * 一月的毫秒值，用于判断上次的更新时间
     */
    private static final long ONE_MONTH = 30 * ONE_DAY;
    /**
     * 一年的毫秒值，用于判断上次的更新时间
     */
    private static final long ONE_YEAR  = 12 * ONE_MONTH;
    /**
     * 上次更新时间的字符串常量，用于作为sharedpreference的键值
     */
    private static final String UPDATE_AT = "update_at";
    /**
     * 用于存储上次更新时间
     */
    private SharedPreferences preferences;
    /**
     * 下拉头的View
     */
    private View header;
    /**
     * 需要去下拉刷新的ListView
     */
    private ListView listView;
    /**
     * 刷新时显示的进度条
     */
    private ProgressBar progressBar;
    /**
     * 指下拉和释放的箭头
     */
    private ImageView arrow;
    /**
     * 下拉和释放的文字描述
     */
    private TextView description;
    /**
     * 上次下拉刷新的时间
     */
    private TextView tvUpdateAt;
    /**
     * 下拉头的布局参数
     */
    private MarginLayoutParams headerLayoutParams;
    /**
     * 上次更新的毫秒值
     */
    private long lastUpdateTime;
    /**
     * 为了防止不同界面的
     */
    private int mId = -1;
    /**
     * 需要header在Y坐标移动的距离,这里为负下拉头的高度
     */
    private int hideHeaderHeight;
    /**
     * 当前状态
     */
    private int currentStatus;
    /**
     * 上一次状态，避免进行重复操作
     */
    private int lastStatus = currentStatus;
    /**
     * 手指按下时的屏幕y坐标
     */
    private float yDown;
    /**
     * 在被判断为滚动之前用户手指可以移动的最大值
     */
    private int touchSlop;
    /**
     * 是否已加载过一次layout，这里onLayout中的初始化只需要加载一次
     */
    private boolean loadOnce;
    /**
     * 是否可以下拉，只有ListView滚到头时，才允许下拉
     */
    private boolean ableToPull;
    /**
     * 下拉刷新的回调
     */
    private PullToRefreshListener listener;

    /**
     * 在运行时动态添加一个下拉头的布局
     * @param context
     * @param attrs
     */
    public RefreshableView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.e(TAG, "RefreshableView: ");
        preferences = PreferenceManager.getDefaultSharedPreferences(context);
        // todo 这里为什么要设置true
        header = LayoutInflater.from(context).inflate(R.layout.header_refresh, null, true);
        findHeaderView(header);
        //得到系统可以判定为滑动的最小距离
        touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        //TODO 这里为什么要调用呢？
        refreshUpdateAtValue();
        //设置该下拉刷新控件的方便，必须是垂直方向，避免在xml中写成水平方向，而不能使用
        setOrientation(VERTICAL);
        addView(header, 0);
    }

    /**
     * 进行一些关键性的初始化操作，比如将下拉头向上偏移进行隐藏，给ListView注册touch事件
     * 一定要在onLayout才能获取header的高度
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        //加载过一次，就不加载了
        if (changed && !loadOnce) {
            hideHeaderHeight = -header.getHeight();
            headerLayoutParams = (MarginLayoutParams) header.getLayoutParams();
            headerLayoutParams.topMargin = hideHeaderHeight;
            //因为已经在构造方法里加入了一个下拉头进去，所以Xml中的RefreshableView的ListView为第2个元素
            if (getChildCount() >= 2 && getChildAt(1) instanceof ListView) {
                listView = (ListView) getChildAt(1);
                listView.setOnTouchListener(this);
            }
            loadOnce = true;
        }
    }

    /**
     * 找到头部控件中的控件
     */
    private void findHeaderView(View header) {
        progressBar = (ProgressBar) header.findViewById(R.id.pb_progress);
        arrow = (ImageView) header.findViewById(R.id.arrow);
        description = (TextView) header.findViewById(R.id.tv_description);
        tvUpdateAt = (TextView) header.findViewById(R.id.tv_updated);
    }

    /**
     * 刷新下拉头中上次更新时间的文字描述
     */
    private void refreshUpdateAtValue(){
        //这里需要加mId，是为了避免不同页面
        lastUpdateTime = preferences.getLong(UPDATE_AT + mId, -1);
        long timePassed = System.currentTimeMillis() - lastUpdateTime;
        long timeIntoFormat;
        String updateAtValue = "";
        if (lastUpdateTime == -1) {
            updateAtValue = getResources().getString(R.string.not_updated_yet);
        } else if (timePassed < 0) {
            updateAtValue = getResources().getString(R.string.time_error);
        } else if (timePassed < ONE_MINUTE) {
            timeIntoFormat = timePassed / ONE_MINUTE;
            String value = timeIntoFormat + "分钟";
            //xml中使用%n$s，其中n表示位置，例如%1$s,则可用String.format(getResources().getString(R.string.xxx), value);
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_HOUR) {
            timeIntoFormat = timePassed / ONE_HOUR;
            String value = timeIntoFormat + "小时";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_DAY) {
            timeIntoFormat = timePassed / ONE_DAY;
            String value = timeIntoFormat + "天";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_MONTH) {
            timeIntoFormat = timePassed / ONE_MONTH;
            String value = timeIntoFormat + "个月";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        } else if (timePassed < ONE_YEAR) {
            timeIntoFormat = timePassed / ONE_YEAR;
            String value = timeIntoFormat + "年";
            updateAtValue = String.format(getResources().getString(R.string.updated_at), value);
        }
        tvUpdateAt.setText(updateAtValue);
    }

    /**
     * 当ListView被触摸时调用，其中处理了各种下拉刷新的具体逻辑
     * Down事件，记录手指按下坐标
     * Move事件，判断当前手势意图
     * UP事件，根据当前手势意图，执行相应刷新UI操作
     * 所有的事件，都需要更新下拉头的信息
     * @param view
     * @param event
     * @return
     */
    @Override
    public boolean onTouch(View view, MotionEvent event) {
        if (currentStatus == STATUS_REFRESHING) {
            return true;
        }
        setIsAbleToPull(event);
        if (ableToPull) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    yDown = event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    float yMove = event.getRawY();
                    int distance = (int) (yMove - yDown);
                    Log.e(TAG, "onTouch: distance" + distance + "touchSlop:" + touchSlop);
                    //TODO 如果手指是上滑状态，并且下拉头是完全隐藏的，就屏蔽下拉事件
                    /*if (distance <= 0 && headerLayoutParams.topMargin < hideHeaderHeight) {
                        return false;
                    }*/
                    if (distance < touchSlop) {
                        return false;
                    }
                    //如果当前状态不是正在刷新
                    if (currentStatus != STATUS_REFRESHING) {
                        if (headerLayoutParams.topMargin > 0) { //下拉头完成离开顶部，判断为，释放刷新
                            currentStatus = STATUS_RELAEASE_TO_REFRESH;
                        } else{ //下拉头没有完全离开顶部，判断为下拉可以去刷新
                            currentStatus = STATUS_PULL_TO_REFRESH;
                        }
                        //通过偏移下拉头的topMargin，来实现下拉效果
                        headerLayoutParams.topMargin = (distance / 2) + hideHeaderHeight;
                        header.setLayoutParams(headerLayoutParams);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                    default:
                        if (currentStatus == STATUS_RELAEASE_TO_REFRESH) {
                            //松手时，如果是释放立即刷新状态，就去调用正在刷新的任务
                            new RefreshingTask().execute();
                        } else if (currentStatus == STATUS_PULL_TO_REFRESH){
                            //松手时，如果是下拉状态，就读调用隐藏下拉头的任务
                            new HideHeaderTask().execute();
                        }

                    break;
            }
            //时刻记得更新下拉头中的信息
            if (currentStatus == STATUS_PULL_TO_REFRESH
                    ||currentStatus == STATUS_RELAEASE_TO_REFRESH) {
                updateHeaderView();
                //当前处于下拉或释放状态，要让ListView失去焦点，否则被点击的那一项会一直处于选中状态
                listView.setPressed(false);
                listView.setFocusable(false);
                listView.setFocusableInTouchMode(false);
                lastStatus = currentStatus;
                //当前正处于下拉或释放状态，返回true屏蔽掉ListView的滚动事件
                return true;
            }
        }
        return false;
    }

    /**
     * 更新下拉头中的信息
     */
    private void updateHeaderView() {
        if (lastStatus != currentStatus) {
            if (currentStatus == STATUS_PULL_TO_REFRESH) {
                description.setText(getResources().getString(R.string.pull_to_refresh));
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                rateArrow();
            } else if (currentStatus == STATUS_RELAEASE_TO_REFRESH) {
                description.setText(getResources().getString(R.string.release_to_refresh));
                arrow.setVisibility(View.VISIBLE);
                progressBar.setVisibility(View.GONE);
                rateArrow();
            } else if (currentStatus == STATUS_REFRESHING) {
                description.setText(getResources().getString(R.string.refreshing));
                progressBar.setVisibility(View.VISIBLE);
                arrow.clearAnimation();
                arrow.setVisibility(View.GONE);
            }
            refreshUpdateAtValue();
        }
    }

    /**
     * 根据当前的状态来旋转箭头
     */
    private void rateArrow() {
        float pivotX = arrow.getWidth() / 2f;
        float pivotY = arrow.getHeight() / 2f;
        float fromDegress = 0f;
        float toDegress = 0f;
        if (currentStatus == STATUS_PULL_TO_REFRESH) {
            fromDegress = 180f;
            toDegress = 360f;
        } else if (currentStatus == STATUS_PULL_TO_REFRESH) {
            fromDegress = 0f;
            toDegress = 180f;
        }
        RotateAnimation animation = new RotateAnimation(fromDegress, toDegress, pivotX, pivotY);
        animation.setDuration(100);
        arrow.startAnimation(animation);

    }

    /**
     * 根据当前ListView的滚动状态来设定{@link #ableToPull}的值
     *每次都需要在onTouch中第一个执行，这样就可以判断出当前应该是滚动ListView，还是应该进行下拉
     * @param event
     */
    private void setIsAbleToPull(MotionEvent event) {
        if (listView != null) {
            View firstChild = listView.getChildAt(0);
            if (firstChild != null) {
                //listView.getFirstVisiblePostion返回值，是ListView第一个可见的item序号，例如：0、1、2...
                //ListView的第一个item的getTop，是Item针对ListView上边缘的距离，当第一个条目可见时，为0；当第一个条目被移除屏幕时，为负数。
                int firstVisiblePos = listView.getFirstVisiblePosition();
                Log.e(TAG, "setIsAbleToPull: firstVisiblePos:" +firstVisiblePos + ",firstChild.getTop:" + firstChild.getTop() );
                if (firstVisiblePos == 0 && firstChild.getTop() == 0) {
                    if (!ableToPull) {
                        yDown = event.getRawY();
                    }
                    //如果首个元素的上边缘，距离父布局值为0,说明ListView滚动到了最顶部，此时应该允许下拉刷新
                    ableToPull = true;
                } else {
                    if (headerLayoutParams.topMargin != hideHeaderHeight) {
                        headerLayoutParams.topMargin = hideHeaderHeight;
                        header.setLayoutParams(headerLayoutParams);
                    }
                    ableToPull = false;
                }
            } else {
                //如果ListView为空，也应该允许下拉刷新
                ableToPull = true;
            }
        }
        Log.e(TAG, "setIsAbleToPull: ableToPull:" + ableToPull );
    }

    /**
     * 正在刷新的任务，在此任务中会回调注册进来的下拉刷新监听器
     * 这时候，Header的topMargin肯定大于0,需要在while循环递减topMargin, 直到topMargin为0
     */
    class RefreshingTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected Void doInBackground(Void... voids) {
            int topMargin = headerLayoutParams.topMargin;
            //循环去更新UI视图，header逐渐回到顶部
            while (true) {
                topMargin = topMargin + SCROLL_SPEED;
                if (topMargin <= 0) {
                    topMargin = 0;
                    break;
                }
                publishProgress(topMargin);
                sleep(10);
            }
            //状态改为正在刷新，如果有回调方法，调用注册监听中的回调方法
            currentStatus = STATUS_REFRESHING;
            publishProgress(0);
            if (listener != null) {
                listener.onRefresh();
            }
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... topMargin) {
            updateHeaderView();
            headerLayoutParams.topMargin = topMargin[0];
            header.setLayoutParams(headerLayoutParams);
        }
    }
    /**
     * 隐藏下拉头的任务，当未进行下拉，或下拉刷新完成以后，此任务将会使下拉头重新隐藏
     * 这时候，topMargin <＝ 0, 逐步减少致－下拉头控价高度
     */
    class HideHeaderTask extends AsyncTask<Void, Integer, Integer> {

        @Override
        protected Integer doInBackground(Void... voids) {
            int topMargin = headerLayoutParams.topMargin;
            while (true) {
                topMargin = topMargin + SCROLL_SPEED;
                if (topMargin <= hideHeaderHeight) {
                    topMargin = hideHeaderHeight;
                    break;
                }
                publishProgress(topMargin);
                sleep(10);
            }
            return topMargin;
        }

        @Override
        protected void onProgressUpdate(Integer... topMargin) {
            headerLayoutParams.topMargin = topMargin[0];
            header.setLayoutParams(headerLayoutParams);
        }

        @Override
        protected void onPostExecute(Integer topMargin) {
            headerLayoutParams.topMargin = topMargin;
            header.setLayoutParams(headerLayoutParams);
            currentStatus = STATUS_REFRESH_FINISHED;
        }
    }

    /**
     * 当前线程睡眠的毫秒数，用于显示渐变
     * @param time
     */
    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 当所有的刷新逻辑完成时，记录调用一下，否则LustView将一直处于正在刷新的状态
     */
    public void finishRefreshing() {
        currentStatus = STATUS_REFRESH_FINISHED;
        preferences.edit().putLong(UPDATE_AT + mId, System.currentTimeMillis()).commit();
        new HideHeaderTask().execute();
    }

    /**
     * 为下拉刷新控件注册一个监听器
     * @param listener
     * @param id
     */
    public void setOnRefreshListener(PullToRefreshListener listener, int id) {
        this.listener = listener;
        mId = id;
    }

    /**
     * 下拉刷新的监听器，使用下拉刷新的地方，应该注册此监听器来获取刷新回调
     */
    public interface  PullToRefreshListener {
        /**
         * 刷新时会回调此方法，在方法内填写具体的刷新逻辑，注意此方法是在子线程中操作的，不必另外开线程来进行耗时操作
         */
        void onRefresh();
    }

}
